<?php

namespace app\common\service;

use app\admin\model\StorageChunks;
use app\admin\model\StoragePath;
use app\common\exception\StorageServiceException;
use app\common\storage\CommonStorage;
use app\common\storage\ReaderStorage;
use app\common\storage\WriterStorage;
use think\facade\Log;

class StorageService
{


    protected $modelUser;

    protected $needPathFix = false;

    public function __construct($model_user = null, $need_path_fix = true)
    {
        $this->modelUser = $model_user;
        $this->needPathFix = $need_path_fix;
    }


    /**
     * 读取文件
     *
     * @param string $path
     * @return void
     */
    public function readByPath(string $path)
    {
        $model_path = $this->initModelPath($path);

        return $this->readByModel($model_path);
    }

    public function readByModel(StoragePath $model_path)
    {
        $content = (new ReaderStorage($model_path))->string();
        return $content;
    }

    public function readStreamByPath(string $path)
    {
        $model_path = $this->initModelPath($path);

        return $this->readStreamByModel($model_path);
    }

    public function readStreamByModel(StoragePath $model_path)
    {
        $stream = (new ReaderStorage($model_path))->stream();
        return $stream;
    }


    /**
     * 写入文件
     *
     * @param string $path
     * @return void
     */
    public function writeByPath(string $path, $file)
    {
        $model_path = $this->initModelPath($path);
        return $this->writeByModel($model_path, $file);
    }

    public function writeByModel(StoragePath $model_path, $file)
    {
        (new WriterStorage($model_path, $file))->save();

        return true;
    }

    public function writeStreamByPath(string $path, $file_stream)
    {
        $model_path = $this->initModelPath($path);

        return $this->writeStreamByModel($model_path, $file_stream);
    }

    public function writeStreamByModel(StoragePath $model_path, $file_stream)
    {
        (new WriterStorage($model_path, $file_stream))->save();

        return true;
    }


    public function createFileByPath($path, $child_name, $data = null)
    {
        $model_path = $this->initModelPath($path);
        return $this->createFileByModel($model_path, $child_name, $data);
    }

    public function createFileByModel(StoragePath $model_path, $child_name, $data = null)
    {

        $new_path = $model_path->path . '/' . $child_name;
        $model_new_path = new StoragePath();

        $model_new_path->path = $new_path;

        $model_new_path->user_id = $model_path->user_id;

        $model_new_path->type = 'node';

        $model_new_path->pid = $model_path->id;

        $model_new_path->chunk_list_md5 = md5('');

        $model_new_path->save();

        if (!is_null($data)) {
            CommonStorage::write($model_new_path, $data);
        }
        $model_new_path->save();

        return $model_new_path;
    }


    /**
     * 创建目录
     *
     * @param string $path
     * @param string $child_name
     * @return void
     */
    public function createDirectoryByPath(string $path, string $child_name)
    {
        $model_path = $this->initModelPath($path);


        return $this->createDirectoryByModel($model_path, $child_name);
    }

    public function createDirectoryByModel(StoragePath $model_path, string $child_name)
    {
        $new_path = $model_path->path . '/' . trim($child_name, '/');

        $new_model_path = new StoragePath();
        $new_model_path->path = $new_path;
        $new_model_path->type = 'collection';
        $new_model_path->user_id = $this->modelUser->id;
        $new_model_path->pid = $model_path->id;
        $new_model_path->save();

        return $new_model_path;
    }

    /**
     * 删除文件
     *
     * @param string $path
     * @return void
     */
    public function deleteByPath(string $path, $delete_any = false)
    {
        $model_path = $this->initModelPath($path);

        return $this->deleteByModel($model_path, $delete_any);
    }

    public function deleteByModel(StoragePath $model_path, $delete_any = false)
    {
        Log::debug($model_path);
        if ($model_path->type != 'node') {

            if ($delete_any) {
                return $this->deleteDirectoryByModel($model_path);
            } else {
                throw new StorageServiceException('删除失败，只能删除文件节点');
            }
        }

        (new WriterStorage($model_path, ''))->save();

        $model_path->delete();

        Log::debug('删除成功');

        return true;
    }

    public function deleteDirectoryByPath(string $path)
    {
        $model_path = $this->initModelPath($path);

        return $this->deleteDirectoryByModel($model_path);
    }

    public function deleteDirectoryByModel(StoragePath $model_path)
    {
        // 删除目录
        if ($model_path->type != 'collection') {
            throw new StorageServiceException('删除失败，只能删除目录节点');
        }


        $list_nodes = $this->listContentsByModel($model_path);

        foreach ($list_nodes as  $model_node) {
            if ($model_node->type == 'node') {
                $this->deleteByModel($model_node);
            } else if ($model_node->type == 'collection') {
                $this->deleteDirectoryByModel($model_node);
            }
        }

        $model_path->delete();

        return true;
    }

    /**
     * 查询目录下的节点列表
     *
     * @param string $path
     * @param boolean $recursive
     * @param array $type
     * @return void
     */
    public function listContentsByPath(string $path, $recursive = false, $type = ['collection', 'node'])
    {
        $model_path = $this->initModelPath($path);

        return $this->listContentsByModel($model_path, $recursive, $type);
    }

    public function listContentsByModel(StoragePath $model_path, $recursive = false, $type = ['collection', 'node'])
    {
        $model_list_nodes = StoragePath::order('path', 'asc');
        $model_list_nodes->where('user_id', $this->modelUser->id);
        if ($recursive) {
            $model_list_nodes->whereLike('path', "{$model_path->path}%");
            $model_list_nodes->autoCache('list_recursive_child_by_path', $model_path->path);
        } else {
            $model_list_nodes->where('pid', $model_path->id);
            $model_list_nodes->autoCache('list_child_by_pid', $model_path->id);
        }

        $list_nodes = $model_list_nodes->select();

        $list_nodes = $list_nodes->whereIn('type', $type);

        return $list_nodes;
    }

    public function getChildByPath($path, $name)
    {
        $model_path = $this->initModelPath($path);

        return $this->getChildByModel($model_path, $name);
    }

    public function getChildByModel($model_path, $name)
    {
        $new_path = $model_path->path . '/' . $name;

        $child_model_path = $this->initModelPath($new_path);

        return $child_model_path;
    }

    public function searchFiles(string $path = '/', $where = [], $order = ['path' => 'asc'])
    {
        # code...
        // TODO:接口用的，临时不需要
    }

    /**
     * 移动文件
     *
     * @param string $path
     * @param [type] $new_path
     * @return void
     */
    public function moveByPath(string $path, $new_path, $force = false, $move_anyway = false)
    {

        $model_path = $this->initModelPath($path);

        return $this->moveByModel($model_path, $new_path, $force, $move_anyway);
    }
    public function moveByModel(StoragePath $model_path, $new_path, $force = false, $move_anyway = false)
    {

        if ($model_path->type != 'node') {

            if ($move_anyway) {
                return $this->moveDirectoryByModel($model_path, $new_path, $force);
            } else {
                throw new StorageServiceException('移动失败，只能移动文件节点');
            }
        }


        $new_model_path = $this->initModelPath($new_path);

        if (!empty($new_model_path)) {

            if (!$force) {
                throw new StorageServiceException('移动失败，目标位置已存在');
            }

            $this->deleteByModel($new_model_path, true);
        }

        $new_path_parent_path = $this->getParentPath($new_path);

        $new_path_parent_path_model = $this->initModelPath($new_path_parent_path);

        if (empty($new_path_parent_path_model)) {
            if (!$force) {
            }
            throw new StorageServiceException('移动失败，目录不存在：' . $new_path_parent_path);

            // TODO：强制移动时，建立目录
        }

        $model_path->path = $new_path;

        $model_path->pid = $new_path_parent_path_model->id;

        $model_path->save();
    }

    public function moveDirectoryByPath(string $path, $new_path, $force = true)
    {
        $model_path = $this->initModelPath($path);

        return $this->moveDirectoryByModel($model_path, $new_path, $force);
    }

    /**
     * 移动目录
     *
     * @param StoragePath $model_path
     * @param [type] $new_path
     * @param boolean $force 为true时，同名目录合并，否则不进行移动操作
     * @return void
     */
    public function moveDirectoryByModel(StoragePath $model_path, $new_path, $force = true)
    {
        $new_model_path = $this->initModelPath($new_path);

        $list_child = $this->listContentsByModel($model_path);

        if (!empty($new_model_path)) {

            // if (!$force) {
            //     throw new StorageServiceException('移动失败，目标位置已存在');
            // }



            // 新目录不能是文件
            if ($new_model_path->type == 'node') {
                throw new StorageServiceException('移动失败，已存在同名文件');
            }

            $model_path->delete();
        } else {
            // 目标名称不存在

            // 检查父目录
            $new_path_parent_path = $this->getParentPath($new_path);

            $new_path_parent_path_model = $this->initModelPath($new_path_parent_path);

            if (empty($new_path_parent_path_model)) {
                if (!$force) {
                }
                throw new StorageServiceException('移动失败，目录不存在：' . $new_path_parent_path);

                // TODO：强制移动时，建立目录
            }

            $model_path->path = $new_path;

            $model_path->pid = $new_path_parent_path_model->id;

            $model_path->save();



            $new_model_path = $model_path;
        }

        // 合并子文件

        foreach ($list_child as  $model_child) {
            $child_name = $this->getPathName($model_child->path);

            $child_new_path = $new_model_path->path . '/' . $child_name;

            $this->moveByModel($model_child, $child_new_path, true, true);
        }
    }
    public function copyByPath(string $path, $new_path, $copy_anyway = false)
    {
        $model_path = $this->initModelPath($path);

        return $this->copyByModel($model_path, $new_path, $copy_anyway);
    }
    public function copyByModel(StoragePath $model_path, $new_path, $copy_anyway = false)
    {

        if ($model_path->type != 'node') {
            if ($copy_anyway) {
                return $this->copyDirectoryByModel($model_path, $new_path);
            } else {
                throw new StorageServiceException('只能复制文件节点');
            }
        }

        $new_model_path = $this->initModelPath($new_path);

        if (!empty($new_model_path)) {
            throw new StorageServiceException('目标已存在同名文件');
        }

        $new_path_parent_path = $this->getParentPath($new_path);

        $new_path_parent_path_model = $this->initModelPath($new_path_parent_path);

        $new_model_path = new StoragePath();

        $new_model_path->path = $new_path;
        $new_model_path->pid = $new_path_parent_path_model->id;

        $copy_attrs = [
            'type',
            'user_id',
            'size',
            'properties',
            'space',
            'etag',
            'content_type',
            'chunk_list_md5',
        ];

        foreach ($copy_attrs as  $attr_name) {
            $new_model_path->setAttr($attr_name, $model_path->getAttr($attr_name));
        }

        $new_model_path->save();

        $list_old_chunks = StorageChunks::where('storage_path_id', $new_model_path->id)->select();

        foreach ($list_old_chunks as  $model_old_chunks) {
            $model_new_chunks = new StorageChunks();

            $model_new_chunks->storage_path_id = $new_model_path->id;
            $model_new_chunks->sort = $model_old_chunks->sort;
            $model_new_chunks->chunk_md5 = $model_old_chunks->chunk_md5;

            $model_new_chunks->save();
        }
    }
    public function copyDirectoryByPath(string $path, $new_path)
    {
        $model_path = $this->initModelPath($path);

        return $this->copyDirectoryByModel($model_path, $new_path);
    }

    public function copyDirectoryByModel(StoragePath $model_path, $new_path)
    {
        if ($model_path->type != 'collection') {
            throw new StorageServiceException('只能复制目录');
        }

        $list_child = $this->listContentsByModel($model_path);
        $new_model_path = $this->initModelPath($new_path);

        if (empty($new_model_path)) {
            $new_path_parent_path = $this->getParentPath($new_path);

            $new_path_parent_path_model = $this->initModelPath($new_path_parent_path);

            if (empty($new_path_parent_path_model)) {
                throw new StorageServiceException('移动失败，目录不存在：' . $new_path_parent_path);

                // TODO：强制移动时，建立目录
            }
            $new_model_path = new StoragePath();

            $new_model_path->path = $new_path;
            $new_model_path->type = 'collection';
            $new_model_path->user_id = $this->modelUser->id;
            $new_model_path->pid = $new_path_parent_path_model->id;

            $new_model_path->save();

        } 

        // 合并子文件

        foreach ($list_child as  $model_child) {
            $child_name = $this->getPathName($model_child->path);

            $child_new_path = $new_model_path->path . '/' . $child_name;

            $this->copyByModel($model_child, $child_new_path, true);
        }
    }

    public function fileExists($path)
    {
        # code...
    }

    public function directoryExists($path)
    {
        # code...
    }

    /**
     * 查看路径是否存在
     *
     * @param [type] $path
     * @return boolean
     */
    public function has($path)
    {
        # code...
    }

    /**
     * 移动或复制时，检查能否安全迁移
     *
     * @param [type] $path
     * @param [type] $new_path
     * @param bool $test_any 设置为true时，path既可以是文件也可以是目录
     * @return void
     */
    public function migrateByPathTest($path, $new_path, $test_any = false)
    {
        # code...
    }

    public function migrateByModelTest($model_path, $new_path, $test_any = false)
    {
        # code...
    }
    public function migrateDirectoryByPathTest($path, $new_path, $test_any = false)
    {
        # code...
    }

    public function migrateDirectoryByModelTest($model_path, $new_path, $test_any = false)
    {
        # code...
    }

    public function initModelPath($path)
    {
        $full_path = trim($path, '/');

        if ($this->needPathFix) {
            $full_path = $this->modelUser->home_path . '/' . $full_path;
        }

        $model_path = StoragePath::autoCache('full_path', $full_path)->where('path', $full_path)->find();

        return $model_path;
    }

    public function getParentPath($path)
    {
        $path_info = explode('/', $path);

        array_pop($path_info);

        return implode('/', $path_info);
    }

    public function getPathName($path)
    {
        $path_info = explode('/', $path);

        return array_pop($path_info);
    }
}
